#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <float.h>

#include "pub_lib.h"
#include "ray_trace.h"

//#define DEBUG_VER

//background color
const Vector3 back_col = {0.235294, 0.67451, 0.843137};
Vector3 geye_pos = {-1, 5, 10};
struct Light gLights[] = {
	{{-20, 70, 20}, {1, 1, 1}},
	{{20, 70, 20},  {1, 1, 1}}
};
const Vector3 material_col = {0.5, 0.5, 0.5};
const int screen_width = 1280;
const int screen_height = 960;

//global variables
//BVH related
struct Triangle *gTriangle; //head of triangles[]
struct Scene gScene;
struct MeshTriangle gbunny;
Model gmodel;

//return normal of triangle
void triangle_getSurfaceProperties(struct Object* obj,
	const Vector3 P, const Vector3 dir, const uint32_t index,
	Vector2* uv, Vector3* N, Vector2* st)
{
	struct Triangle* p;
	p = gTriangle + index;
	*N = p->normal;
}

Vector3 triangle_evalDiffuseColor(struct Object* obj, const Vector2 st)
{
	return material_col;
}

struct Bounds3 triangle_get_bounds(struct Triangle* pa)
{
	struct Bounds3 ret;
	ret.pMin = get_min(pa->v0, pa->v1);
	ret.pMin = get_min(ret.pMin, pa->v2);

	ret.pMax = get_max(pa->v0, pa->v1);
	ret.pMax = get_max(ret.pMax, pa->v2);
	return ret;
}

//init 3 vertices of a triangle
//get min and max
void triangle_get_vertexs(float* val, Vector3* dest)
{
	Vector3 v1 = {val[0], val[1], val[2]};
	Vector3 v2 = {val[3], val[4], val[5]};
	Vector3 v3 = {val[6], val[7], val[8]};
	Vector3* dst = (Vector3*)dest;
	float scale = 60.0f;
	dst[0] = Vector3Scale(v1, scale);
	dst[1] = Vector3Scale(v2, scale);
	dst[2] = Vector3Scale(v3, scale);
}

void triangle_material_init(struct Triangle *pTri)
{
	pTri->m.m_type = DIFFUSE_AND_GLOSSY;
	pTri->m.m_color = material_col;
	pTri->m.m_emission = Vector3Zero();;
	pTri->m.Kd = 0.6f;
	pTri->m.Ks = 0.0f;
	pTri->m.specularExponent = 0;
}

//calc normal
void triangle_get_normal(struct Triangle *p)
{
	p->e1 = Vector3Subtract(p->v1, p->v0);
    p->e2 = Vector3Subtract(p->v2, p->v0);
	p->normal = Vector3CrossProduct(p->e1, p->e2);
    p->normal = Vector3Normalize(p->normal);
}

void init_triangle(struct Triangle *p, Vector3* face)
{
	triangle_material_init(p);

	p->v0 = face[0];
	p->v1 = face[1];
	p->v2 = face[2];
	//calc center: (v0+v1+v2)/3
	p->center = Vector3Add(p->v0, p->v1);
	p->center = Vector3Add(p->center, p->v2);
	p->center = Vector3Scale(p->center, 1.0f/3.0f);

	p->box = triangle_get_bounds(p);
	triangle_get_normal(p);
}

//load model data from file and fill structure
void fill_mesh(struct MeshTriangle *p, Model* model)
{
	//traverse all triangles defined by OBJ file
	Mesh *mesh = model->meshes;

	int i;
	float *v;
	Vector3 face_vertices[3];
	struct Triangle *pTri;
	gTriangle = calloc(mesh->triangleCount, sizeof(struct Triangle));
	p->numTriangles = mesh->triangleCount;

	for(int index = 0; index < mesh->triangleCount; index++) {
		i = index * 9; //triangle has 3 vertices, every vertex has (x,y,z)
		v = &(mesh->vertices[i]);
		triangle_get_vertexs(v, face_vertices);
		pTri = gTriangle + index;
		pTri->index = index; //redundancy info used BVH
		init_triangle(pTri, face_vertices);
	}
}

bool mytriangle_intersect(struct Object* obj,
	const Vector3 orig, const Vector3 dir,
	float* tnear, uint32_t* index, Vector2* uv)
{
	struct MeshTriangle *p = (struct MeshTriangle*)obj;

	bool intersect = false;
	Vector3 v0, v1, v2;
	float t, u, v;

	struct Triangle *pTri;
	for (uint32_t k = 0; k < p->numTriangles; k++)
	{
		pTri = gTriangle + k;
		v0 = pTri->v0;
		v1 = pTri->v1;
		v2 = pTri->v2;
		if (rayTriangleIntersect(v0, v1, v2, orig, dir, &t, &u, &v) && t < *tnear)
		{
			*tnear = t;
			*index = k;
			intersect |= true;
		}
	}
	return intersect;
}

void init_mesh(struct MeshTriangle *p, char* file)
{
	p->obj.Kd = 0.6f;
	p->obj.intersect = bvh_intersect; //BVH accelerate
	//p->obj.intersect  = mytriangle_intersect; //no BVH accelerate
	p->obj.getSurfaceProperties = triangle_getSurfaceProperties;
	p->obj.evalDiffuseColor = triangle_evalDiffuseColor;

	Model model;
	model = LoadModel(file);
	fill_mesh(p, &model);
	UnloadModel(model);
}

// In the main function of the program, we create the scene (create objects and lights)
// as well as set the options for the render (image width and height, maximum recursion
// depth, field-of-view, etc.). We then call the render function().
void init_scene(struct Scene *pGscene)
{
	INIT_LIST_HEAD(&pGscene->objects);

	pGscene->eye_pos = geye_pos;
	pGscene->fov = 90;
	/* 1280 * 960*/
	pGscene->width = screen_width;
	pGscene->height = screen_height;

    pGscene->backgroundColor = back_col;
    pGscene->maxDepth = 5;
    pGscene->bias = 0.00001;

	//init Modules
	init_mesh(&gbunny, "./res/homework6/bunny.obj");
	//add objects to scene, only one object: mesh
	add_object(gbunny, pGscene);
	pGscene->lights = gLights;

	int count = pGscene->width * pGscene->height;
	pGscene->framebuffer = calloc(count, sizeof(Color));

#ifdef DEBUG_VER
	gmodel = LoadModel("./res/homework6/bunny.obj");
#endif
}

void exit_scene(struct Scene *p)
{
	free(gTriangle);
	free(p->framebuffer);

#ifdef DEBUG_VER
	UnloadModel(gmodel);
#endif

}

void draw_scene(struct Scene *p)
{
	int i, j, index;

	for(i = 0; i < p->width; i++) {
		for ( j = 0; j < p->height; j++) {
			index = get_point_index(i, j, p->width, p->height);
			if (sampled_point(p->framebuffer[index])) {
				DrawPixel(i, j, p->framebuffer[index]);
			}
		}
	}
}

int main(int argc, char** argv)
{
	// Initialization
	char key;
	//--------------------------------------------------------------------------------------

	InitWindow(screen_width, screen_height, "homework6 - ray tracing - BVH");
	SetTargetFPS(60);               // Set our game to run at 60 frames-per-second

	init_scene(&gScene);
	double start = GetTime();

	buildBVH(&gbunny);
#ifndef DEBUG_VER
	Render(&gScene);
#endif

	double end = GetTime();

	int duration = (int)(end - start);
	int minutes = duration / 60;
	int seconds = duration % 60;
	//--------------------------------------------------------------------------------------

	// Main game loop
	while (!WindowShouldClose())    // Detect window close button or ESC key
	{
		// Update
		//----------------------------------------------------------------------------------
		// TODO: Update your variables here
		key = (char)GetKeyPressed();
		if (key == ESC_KEY) {
			break;
		}

		//----------------------------------------------------------------------------------

		// Draw
		//----------------------------------------------------------------------------------
		BeginDrawing();

#ifdef DEBUG_VER
		draw_model(&gmodel, geye_pos, 90.0f, 50.0f);
#else
		ClearBackground(RAYWHITE);
		draw_scene(&gScene);
		DrawText(TextFormat("%d * %d,  cost Time: %d minutes, %d seconds", \
			gScene.width, gScene.height, minutes, seconds),
			10, 20, 40, BLACK);
#endif
		EndDrawing();
		//----------------------------------------------------------------------------------
	}

	// De-Initialization
	//--------------------------------------------------------------------------------------
	CloseWindow();        // Close window and OpenGL context
	//--------------------------------------------------------------------------------------

	destroyBVH(&gbunny);
	exit_scene(&gScene);
	return 0;
}

